package org.veridiandynamics.server.model;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.veridiandynamics.server.dao.HibernateDriver;
import org.veridiandynamics.server.dao.Queryprocessor;
import org.veridiandynamics.server.domain.Admin;
import org.veridiandynamics.server.domain.Appointment;
import org.veridiandynamics.server.domain.Doctor;
import org.veridiandynamics.server.domain.DoctorsOrders;
import org.veridiandynamics.server.domain.Nurse;
import org.veridiandynamics.server.domain.Patient;
import org.veridiandynamics.server.domain.RoleLookup;
import org.veridiandynamics.server.domain.Roles;
import org.veridiandynamics.server.domain.TreatmentRecord;
import org.veridiandynamics.server.domain.User;

public class ServerConnectionHandler {

	public static void main(String[] args) {

		try {

			Map<String, Object> requestMap = new HashMap<String, Object>();
			ServerSocketChannel serverSocket = ServerSocketChannel.open();
			serverSocket.configureBlocking(false);
			serverSocket.socket().bind(new InetSocketAddress(InetAddress.getLocalHost(), 8000));
			Selector selector = Selector.open();
			serverSocket.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("Server is running");
			ByteBuffer buffer;
			while (true) {
				selector.select();
				Set<SelectionKey> keys = selector.selectedKeys();
				Iterator<SelectionKey> iterator = keys.iterator();
				while(iterator.hasNext()) {
					SelectionKey key = iterator.next();
					iterator.remove();
					if (key.isAcceptable()) {
						SocketChannel client = serverSocket.accept();
						client.configureBlocking(false);
						client.register(selector, SelectionKey.OP_READ);
						continue;
					}

					if (key.isReadable()) {
						SocketChannel client = (SocketChannel) key.channel();
						int BUFFER_SIZE = 4096;
						buffer = ByteBuffer.allocate(BUFFER_SIZE);
						client.read(buffer);
						buffer.flip();
						if (buffer.limit() <= 0) {
							continue;
						}
						ByteArrayInputStream inputByteStream = new ByteArrayInputStream(buffer.array());
						ObjectInputStream inputObjectStream = new ObjectInputStream(inputByteStream);
						requestMap = (HashMap<String, Object>) inputObjectStream.readObject();
						buffer.clear();
						client.register(selector, SelectionKey.OP_WRITE);
						continue;
					}

					if (key.isWritable()) {
						SocketChannel client = (SocketChannel) key.channel();
						int BUFFER_SIZE = 4096;
						buffer = ByteBuffer.allocate(4096);
						ByteArrayOutputStream outputByteStream = new ByteArrayOutputStream();
						ObjectOutputStream outputObjectStream = new ObjectOutputStream(outputByteStream);
						buffer = ByteBuffer.allocate(BUFFER_SIZE);
						Map<String, Object> responseMap = handleRequest(requestMap);
						if (responseMap == null) {
							continue;
						}
						outputObjectStream.writeObject(responseMap);
						buffer = ByteBuffer.wrap(outputByteStream.toByteArray());
						client.write(buffer);
						buffer.clear();
						client.register(selector, SelectionKey.OP_READ);
						client.close();
						continue;


					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public static Map<String,Object> handleRequest(Map<String, Object> requestMap) {
		Map<String, Object> responseMap = new HashMap<String, Object>();

		String requestType = (String) requestMap.get("requestType");

		if (requestType == null) {
			System.out.println("requestType is null");
			return null;
		}

		AdminModel adminModel = new AdminModel();
		AppointmentModel appointmentModel = new AppointmentModel();
		DoctorModel doctorModel = new DoctorModel();
		InvoiceModel invoiceModel = new InvoiceModel();
		NurseModel nurseModel = new NurseModel();
		PatientModel patientModel = new PatientModel();
		ReportModel reportModel = new ReportModel();
		UserModel userModel = new UserModel();

		if (requestType.equals("login")) {
			Roles role;
			String username = (String) requestMap.get("username");
			String password = (String) requestMap.get("password");
			String roleString = (String) requestMap.get("role");
			User loginUser;

			if (roleString.equals("Patient")) {
				role = new Roles(RoleLookup.Role.PATIENT);
				loginUser = (Patient) userModel.login(username, password, role);
			} else if(roleString.equals("Nurse")) {
				role = new Roles(RoleLookup.Role.NURSE);
				loginUser = (Nurse) userModel.login(username, password, role);
			} else if(roleString.equals("Doctor")) {
				role = new Roles(RoleLookup.Role.DOCTOR);
				loginUser = (Doctor) userModel.login(username, password, role);
			} else if(roleString.equals("System Administrator")) {
				role = new Roles(RoleLookup.Role.ADMIN);
				loginUser = (Admin) userModel.login(username, password, role);
			} else {
				role = null;
				loginUser = null;
			}


			responseMap.put("user", loginUser);
			responseMap.put("responseType", "login");

			return responseMap;

		} else if (requestType.equals("makeAppointment")) {
			String patientUsername = (String) requestMap.get("username");
			Appointment appointment = (Appointment) requestMap.get("appointment");
			Patient patient = new Patient();
			long id = Queryprocessor.UsernametoID(patientUsername);
			HibernateDriver driver = new HibernateDriver();
			patient = (Patient) driver.RetrieveObject(id, patient);
			patient.setMyAppointments(new HashSet<Appointment>()); //will fix later
			patient.getMyAppointments().add(appointment);
			driver.UpdateObject(patient);

		} else if (requestType.equals("createPatient")) {
			Patient patient = (Patient) requestMap.get("patient");
			HibernateDriver driver = new HibernateDriver();
			driver.SaveObject(patient);

		} else if (requestType.equals("deletePatient")) {
			String patientUsername = (String) requestMap.get("username");
			HibernateDriver driver = new HibernateDriver();
			long id = Queryprocessor.UsernametoID(patientUsername);
			Patient patient = new Patient();
			patient = (Patient) driver.RetrieveObject(id, patient);
			driver.DeleteObject(patient);

		} else if (requestType.equals("editPatient")) {
			String patientUsername = (String) requestMap.get("username");
			HibernateDriver driver = new HibernateDriver();
			Patient patient = new Patient();
			long id = Queryprocessor.UsernametoID(patientUsername);
			patient = (Patient) driver.RetrieveObject(id, patient);
			
			//this will override everything, which is bad - will fix later
			patient.setLastName((String) requestMap.get("last"));
			patient.setFirstName((String) requestMap.get("first"));
			patient.setMiddleName((String) requestMap.get("middle"));
			patient.setStreetAddress((String) requestMap.get("address"));
			patient.setTelephone((String) requestMap.get("telephone"));
			patient.setCell((String) requestMap.get("cell"));
			patient.setCity((String) requestMap.get("city"));
			patient.setZip(Integer.parseInt((String) requestMap.get("zip")));
			patient.setEmail((String) requestMap.get("email"));
			patient.setPharmacy((String) requestMap.get("pharmacy"));
			patient.setAllergies((String) requestMap.get("allergies"));
			patient.setInsuranceCarrier((String) requestMap.get("insurance"));
			patient.setUsername((String) requestMap.get("setUsername"));
			
			driver.UpdateObject(patient);

		} else if (requestType.equals("viewPatients")) {
			HibernateDriver driver = new HibernateDriver();
			List<Patient> patientList = driver.GetAll(new Patient());
			responseMap.put("patientList", patientList);
			return responseMap;

		} else if (requestType.equals("treatmentRecord")) {
			String username = (String) requestMap.get("username");
			Patient patient = new Patient();
			long id = Queryprocessor.UsernametoID(username);
			HibernateDriver driver = new HibernateDriver();
			patient = (Patient) driver.RetrieveObject(id, patient);	
			TreatmentRecord record = (TreatmentRecord) requestMap.get("treatmentRecord");
			//record.setPatient(patient); I don't have the patient client-side!!
			patient.getMyTreatmentRecordList().add(record);
			driver.UpdateObject(patient);
		
		} else if (requestType.equals("viewAppointments")) {
			HibernateDriver driver = new HibernateDriver();
			List<Appointment> appointmentList = driver.GetAll(new Appointment());
			responseMap.put("appointmentList", appointmentList);
			return responseMap;

		} else if (requestType.equals("addOrders")) {
			DoctorsOrders orders = (DoctorsOrders) requestMap.get("orders");
			Long id = (Long) requestMap.get("treatmentRecordID");
			HibernateDriver driver = new HibernateDriver();
			TreatmentRecord record = new TreatmentRecord();
			record = (TreatmentRecord) driver.RetrieveObject(id, record);
			record.setDoctorsOrders(orders);
			driver.UpdateObject(record);
		
		} else if (requestType.equals("suspendAccount")) {
			String username = (String) requestMap.get("username");
			long id = Queryprocessor.UsernametoID(username);
			HibernateDriver driver = new HibernateDriver();
			String role = (String) requestMap.get("role");
			Object obj = new Object();
			if (role.equals("Patient")) obj = new Patient();
			if (role.equals("Nurse")) obj = new Nurse();
			if (role.equals("Admin")) obj = new Admin();
			if (role.equals("Doctor")) obj = new Doctor();
			((User) obj).setSuspended(true);
			driver.UpdateObject((User) obj); // oy, does this work?

		} else if (requestType.equals("unsuspendAccount")) {
			String username = (String) requestMap.get("username");
			long id = Queryprocessor.UsernametoID(username);
			HibernateDriver driver = new HibernateDriver();
			String role = (String) requestMap.get("role");
			Object obj = new Object();
			if (role.equals("Patient")) obj = new Patient();
			if (role.equals("Nurse")) obj = new Nurse();
			if (role.equals("Admin")) obj = new Admin();
			if (role.equals("Doctor")) obj = new Doctor();
			((User) obj).setSuspended(false);
			driver.UpdateObject((User) obj);

		} else if (requestType.equals("addNurse")) {

		} else if (requestType.equals("addDoctor")) {

		} else if (requestType.equals("addAdmin")) {

		} else if (requestType.equals("deleteNurse")) {

		} else if (requestType.equals("editNurse")) {

		} else if (requestType.equals("setPassword")) {

		} else if (requestType.equals("viewHistory")) {
			String username = (String) requestMap.get("username");
			List<Integer> weightList = patientModel.getWeightHistory(username);
			responseMap.put("weightList", weightList);
			return responseMap;
		} else if (requestType.equals("viewIncome")) {
			//view income statement
			Date date = (Date) requestMap.get("incomeStatement");
			responseMap.put("incomeStatement", reportModel.incomeStatement(date));
			return responseMap;
		}

		return null;

	}
}
